iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0
Modern Web

從Create到React—用來實作使用者介面的JavaScript函式庫系列 第 22

什麼是Flux設計、實作簡單ATM範例了解Redux三大原則feat.vanilla Javascript

  • 分享至 

  • xImage
  •  

本文提及以下內容

  • 前言
  • flu字根
  • flux設計模式-單向資料流
    • 傳統MVC所遇到的問題
    • flux的角色
    • flux優點
  • Redux
    • 動機
    • 三大原則
  • 使用vanilla Javascript ATM範例
    • action物件
    • initState物件
    • reducer函式
    • store
  • 小結

前言

了解Redux的概念可以追溯到先前facebook(現在改名叫做meta)公司的演講,他們提出MVC所遇到的問題以及提出flux的設計概念,本篇文章會重點式講述flux的概念,Redux是flux的概念底下所做出的一種實踐,redudx不單只是只能用在React,他是一種狀態管理的library,因此也會以原生js實作一個簡單的範例來理解redux的原理。

Flu字根

介紹flux之前我們先看flu字根的英文

  • flu開頭源自拉丁文fluere是flow(流動的意思)
    • flush(激流)、fluent(流暢)、fluxion(流動)
  • 與flow組合的字
    • inflow(流入)、overflow(溢出)
  • fl開頭為flow同源的字
    • float(飄動)、flood(洪水)

從英文字母可以了解一切的根源在於,flux主要也是在控管整個資料流動的設計。

Flux設計模式-單向資料流

傳統MVC所遇到的問題

在傳統MVC的設計模式會導致視圖(view)與模型的關係複雜,有可能一個視圖來自於不同的model,也可能導致無限循環的情形產生。如下圖

為了解決資料流混亂的問題,因此提出了flux的設計概念。

如下圖

圖片來源:Flux官方網站-In-Depth Overview

Flux的角色

  1. action 促進資料傳遞給dispatcher的輔助方法(規範改變資料的動作)
  2. dipatcher 收到action和payload廣傳到被註冊的callback(被註冊的store)
  3. store 應用程式狀態和邏輯處理(也就是被註冊的callback)的容器
  4. view 根據資料渲染UI和監聽使用者的事件

flux優點

  • view專注在顯示資料(不用撰寫邏輯)
  • 資料和邏輯統一存放
  • 明確定義每個角色使開發者快速理解App行為

Redux

flux設計模式提出,facebook也開源了flux的專案,但目前官方已經處於維護狀態並且推薦了其他狀態管理的library像是Redux、MobX、Recoil

如下圖

圖片來源:facebook/flux

另外有興趣的人也可以觀看stack overflow的redux開發者的回答Why use Redux over Facebook Flux?更可以了解redux與flux的差別。

接下來講述redux發展的動機。

動機

  • SPA(單頁應用程式)蓬勃發展
  • 狀態多樣化(例如伺服器資料、暫存資料、本地端資料)

隨著Javascript單頁應用程式發展,需要管理許多狀態,這些狀態包含伺服器、資料暫存、本地端沒有正式儲存到伺服器的資料等等,即便react簡化了事件流程,但state還是留給開發者自行管理

state狀態傳遞的時候遇到的瓶頸

在沒有Redux之前狀態是需要在各個component之間傳遞十分麻煩,有了Redux後將狀態統一儲存在store配發給每個component

圖片來源First Day Guide to Redux

UI接收到事件觸發後使用dispatch發送actionstore,store經由所接收到的actionreducer處理對應的action後回傳state,UI接收到state
圖片來源Redux Application Data Flow

三大原則

  • 單一來源

    • 作為應用程式全域的state儲存了Object tree在單一的store裡面
  • State是唯讀屬性

    • state只能透過發送action來改變,如此一來確保view或者fetch的callback都不會直接寫入到state,透過集中管理嚴格照順序的逐一觸發。
  • 修改只能是純函式

    • 撰寫reducer的函式只能是pure function,透過接收先前的state和action回傳新的state

ATM範例

這邊以提款機範例展示Redux

  • 一個帳戶內擁有存款
  • 可以更改狀態(存款)的ATM提款機

提款機範例最小化redux基本要素包含以下

  • action物件
  • initState物件
  • reducer函式
  • store

提款機範例中的角色各司其職

action物件

一個最基礎的action物件,帶有type的屬性,用來作為dispatch的參數,稍後將會被reducer函式接受做出對應的動作。
以此範例而言,action可以存款和提款

{
  type: 'DEPOSIT'
}
{
  type: 'WITHDRAW'
}

initState物件

定義state的初始值的物件,換句話說算是資料的初始值,以此範例為一個key為money,值為1000的物件。

const initState = {
  money: 1000
}

reducer

function ATMReducer(state = initState, action) {
  switch (action.type) {
    case 'DEPOSIT':
      return { money: state.money + 100 }
    case 'WITHDRAW':
      return { money: state.money - 100 }
    default:
      return state
  }
}

reducer作為改變state的函式,負責邏輯處理,這邊接收兩個參數,第一個參數是state的初始值第二個參數是action,透過switch用來辨認等等接收的dispatch的動作是哪一種類型,以此範例的話解析action的type,將state的物件+100或-100,這邊可以發現撰寫方式是immutable,我們不直接更改state裡面的值,而是創造一個新的物件return回去。

store檔案

//初始化一個store將reducer代入
let store = createStore(ATMReducer);
//當改變發生的時候要做什麼事情
//這裡帶入一個callback
//透過store.getState就可以讀取當前state的東西
store.subscribe(() => console.log(store.getState()));
//發送WITHDRAW的action
store.dispatch({
    type: 'WITHDRAW'
});
store.dispatch({
    type: 'DEPOSIT'
});
store.dispatch({
    type: 'WITHDRAW'
});
  • createStore()初始化一個store代入reducer
  • store.subscribe 改變發生的時候要做什麼事情
  • store.dispatch 發送action

完整程式碼如下

import { createStore } from 'redux'
const initState = {
    money: 1000,
}
const depositActionCreator = (payload) => (
    {
        type: 'DEPOSIT',
        payload
    })
const withdrawActionCreator = (payload) => (
    {
        type: 'WITHDRAW', payload
    }
)
function ATMReducer(state = initState, action) {
    switch (action.type) {
        case 'DEPOSIT':
            return { money: state.money + action.payload }
        case 'WITHDRAW':
            return { money: state.money - action.payload }
        default:
            return state
    }
}
let store = createStore(ATMReducer);
store.subscribe(() => console.log(store.getState()));
store.dispatch(depositActionCreator(100));
store.dispatch(withdrawActionCreator(200));
store.dispatch(depositActionCreator(500));

小結

希望透過vanilla javascript可以更了解redux,如果內容有誤歡迎底下留言,希望以上內容對大家有所幫助。

參考資料

上一篇
從實作To Do List理解redux toolkit—分段講解step by step(下)
下一篇
React route如何運作、建立404的路由、巢狀路由、動態路由
系列文
從Create到React—用來實作使用者介面的JavaScript函式庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言